package util

import (
	"crypto/md5"
	"errors"
	"fmt"
	"gitee.com/gopher2011/godemo/global"
	"io"
	"strings"
	"unicode/utf8"
)

// 获取头像 https://zhuanlan.zhihu.com/p/115248957
func Gravatar(avatar string,emailI interface{},size uint16,isHttps bool)string{
	gravatarDomain := "http://gravatar.com"
	if isHttps {
		gravatarDomain = "https://secure.gravatar.com"
	}
	// 获取规范的 cdn域名
	cdnDomain := global.App.CanonicalCDN(isHttps)

	if avatar != "" {
		// 如果 avatar有http前缀
		if strings.HasPrefix(avatar,"http"){
			return fmt.Sprintf("%s&s=%d",avatar,size)
		}
		return fmt.Sprintf("%savatar/%s?imageView2/2/w/%d",cdnDomain,avatar,size) // 这个路径是静态资源的路径。即:访问这个路径就是访问本站服务器的资源.
	}
	email,ok := emailI.(string)
	if !ok {
		return fmt.Sprintf("%savatar/gopher28.png?imageView2/2/w/%d",cdnDomain,size)
	}
	return fmt.Sprintf("%s/avatar/%s?s=%d",gravatarDomain,Md5(email),size)

}

func Md5(text string) string {
	hashMd5 := md5.New()
	io.WriteString(hashMd5, text)
	return fmt.Sprintf("%x", hashMd5.Sum(nil))
}

var (
	outOfRange = errors.New(" utf8string: index out of range")
	sliceOutOfRange = errors.New(" utf8string:slice index out of range")
)

//用一个小结构包装一个常规字符串，
//通过代码点索引提供更有效的索引，而不是字节索引。
//每个索引操作向前或向后增量扫描是 O(1)（虽然没有范围子句前进的速度快）。
//随机访问在字符串的长度上是 O(N)，但是开销比总是从头开始扫描要少。
//如果字符串是 ASCII，随机访问是 O(1)。与内置字符串类型不同，String 具有内部可变状态并且不是线程安全的。
type String struct {
	str string
	numRunes int
	width int
	bytePos int
	runePos int
	nonASCII int
}

func NewString(content string)*String{
	return new(String).Init(content)
}

func (s *String)Init(content string)*String{
	s.str = content
	s.bytePos = 0
	s.runePos = 0
	for i := 0 ;i<len(content);i++ {
		if content[i] >= utf8.RuneSelf {
			// Not ASCII
			s.numRunes = utf8.RuneCountInString(content)
			_, s.width = utf8.DecodeRuneInString(content)
			s.nonASCII = i
			return s
		}
	}
	// 如果上面 for循环中的代码,没有return,则程序会执行到这里
	// ASCII 很简单。此外，空字符串是 ASCII。
	s.numRunes = len(content)
	s.width = 0
	s.nonASCII = len(content)
	return s
}

// Slice 返回在符文位置 [i:j] 处切片的字符串。
func (s *String)Slice(i,j int)string{
	if j < s.nonASCII {
		return s.str[i:j]
	}
	if i < 0 || j > s.numRunes || i > j {
		panic(sliceOutOfRange)
	}
	if i == j {
		return ""
	}
	//对于非 ASCII，在 At(i) 之后，bytePos 始终是索引字符的位置。
	var low,high int
	switch {
	case i < s.nonASCII:
		low = i
	case i == s.numRunes:
		low = len(s.str)
	default:
		s.At(i)
		low = s.bytePos
	}
	switch {
	case j == s.numRunes:
		high = len(s.str)
	default:
		s.At(j)
		high = s.bytePos
	}
	return s.str[low:high]
}

// At returns the rune with index i in the String.  The sequence of runes is the same
// as iterating over the contents with a "for range" clause.
func (s *String) At(i int) rune {
	// ASCII is easy.  Let the compiler catch the indexing error if there is one.
	if i < s.nonASCII {
		return rune(s.str[i])
	}

	// Now we do need to know the index is valid.
	if i < 0 || i >= s.numRunes {
		panic(outOfRange)
	}

	var r rune

	// Five easy common cases: within 1 spot of bytePos/runePos, or the beginning, or the end.
	// With these cases, all scans from beginning or end work in O(1) time per rune.
	switch {

	case i == s.runePos-1: // backing up one rune
		r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
		s.runePos = i
		s.bytePos -= s.width
		return r
	case i == s.runePos+1: // moving ahead one rune
		s.runePos = i
		s.bytePos += s.width
		fallthrough
	case i == s.runePos:
		r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
		return r
	case i == 0: // start of string
		r, s.width = utf8.DecodeRuneInString(s.str)
		s.runePos = 0
		s.bytePos = 0
		return r

	case i == s.numRunes-1: // last rune in string
		r, s.width = utf8.DecodeLastRuneInString(s.str)
		s.runePos = i
		s.bytePos = len(s.str) - s.width
		return r
	}

	// We need to do a linear scan.  There are three places to start from:
	// 1) The beginning
	// 2) bytePos/runePos.
	// 3) The end
	// Choose the closest in rune count, scanning backwards if necessary.
	forward := true
	if i < s.runePos {
		// Between beginning and pos.  Which is closer?
		// Since both i and runePos are guaranteed >= nonASCII, that's the
		// lowest location we need to start from.
		if i < (s.runePos-s.nonASCII)/2 {
			// Scan forward from beginning
			s.bytePos, s.runePos = s.nonASCII, s.nonASCII
		} else {
			// Scan backwards from where we are
			forward = false
		}
	} else {
		// Between pos and end.  Which is closer?
		if i-s.runePos < (s.numRunes-s.runePos)/2 {
			// Scan forward from pos
		} else {
			// Scan backwards from end
			s.bytePos, s.runePos = len(s.str), s.numRunes
			forward = false
		}
	}
	if forward {
		// TODO: Is it much faster to use a range loop for this scan?
		for {
			r, s.width = utf8.DecodeRuneInString(s.str[s.bytePos:])
			if s.runePos == i {
				break
			}
			s.runePos++
			s.bytePos += s.width
		}
	} else {
		for {
			r, s.width = utf8.DecodeLastRuneInString(s.str[0:s.bytePos])
			s.runePos--
			s.bytePos -= s.width
			if s.runePos == i {
				break
			}
		}
	}
	return r
}

// 返回子串
func Substring(str string,length int,suffix string)string {
	if length >= len(str) {
		return str
	}
	utf8Str := NewString(str)
	if length > utf8Str.numRunes {
		return str
	}
	return utf8Str.Slice(0,length) + suffix
}
